package agent.componet.message.file;


import agent.componet.message.util.KryoUtils;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageType;
import dev.langchain4j.memory.ChatMemory;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


@Component
@RequiredArgsConstructor
// 实现动态长度消息存储和自动空间管理。以下是完全重构的方案：
public class FileChatMemory implements ChatMemory {


    private final MsgCommitLog commitLog = MsgCommitLog.getINSTANCE();
    private final IndexQueue indexQueue = IndexQueue.getINSTANCE();
    private final ThreadLocal<UUID> uuidThreadLocal = new ThreadLocal<>();

    public void setUUID(UUID uuid) {
        uuidThreadLocal.set(uuid);
    }

    @Override
    public Object id() {
        return uuidThreadLocal.get();
    }

    @Override
    public void add(ChatMessage chatMessage) {
        UUID id = (UUID) id();

        byte msgType = getMsgType(chatMessage.type());
        byte finishType = 0x1;

        byte[] data = KryoUtils.serialize(chatMessage);


        try {

            Long offset = commitLog.appendMsg(id, data, msgType, finishType);
            indexQueue.addIndex(id, offset);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }


    private byte getMsgType(ChatMessageType type) {

        switch (type) {


        }
        return 0x1;
    }

    @Override
    public List<ChatMessage> messages() {


        UUID id = (UUID) id();

        List<Long> indexes = null;
        try {
            indexes = indexQueue.getIndexes(id);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        List<ChatMessage> collect = indexes.stream().map(x -> {

            try {
                MsgCommitLog.WholeMsg wholeMsg = commitLog.readMsg(x);

                return (ChatMessage) KryoUtils.deserialize(wholeMsg.getData());


            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }).collect(Collectors.toList());

        return collect.isEmpty() ? Collections.emptyList() : collect;

    }


    @Override
    public void clear() {

        UUID id = (UUID) id();
        IndexFile indexFile = indexQueue.getIndexFile(id);
        if (indexFile == null) return;
        indexFile.clearIndexFile(true, 0);  // Clear All The Thing

    }


    static class MsgCommitLog {


        private final static Path fakePath = null;
        private final static int SIZE = 1024 * 1024;
        private final static int MSG_SIZE = 32;
        private final static int HEADER_SIZE = 32;
        @Getter
        private static MsgCommitLog INSTANCE;

        static {

            if (INSTANCE == null) INSTANCE = new MsgCommitLog(fakePath);

        }

        private final Path pathStore;
        private final String PATH_COMMITTAL = "commitlog.txt";
        private FileChannel fileChannel;
        private MappedByteBuffer buffer;
        private long currentFileIndex = 0;
        private long currentOffset = 0;

        public MsgCommitLog(Path pathStore) {
            this.pathStore = pathStore;
            try {
                Files.createDirectories(pathStore);


            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        public void createStoreFile() throws IOException {
            if (buffer != null) {
                buffer.force();
                fileChannel.close();

            }


            Path resolve = pathStore.resolve(PATH_COMMITTAL);

            this.fileChannel = FileChannel.open(resolve, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);

            this.buffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, SIZE);

            currentFileIndex++;
            currentOffset = 0;


        }

        public void init() {
        }

        public synchronized Long appendMsg(UUID uuid, byte[] data, byte msgType, byte finishType) throws IOException {
            // 这里 写锁为了 防止出现一些问题 比如同时append的一些问题 主要是这个currentOffset出现问题
            int content_size = data.length + HEADER_SIZE;

            if (content_size + currentOffset > SIZE) {

                createStoreFile();

            }

            ByteBuffer byteBuffer = ByteBuffer.allocate(HEADER_SIZE);
            byteBuffer.putLong(uuid.getMostSignificantBits());
            byteBuffer.putLong(uuid.getLeastSignificantBits());
            byteBuffer.putInt(content_size);   //注意这里是 总长度
            byteBuffer.put(msgType);
            byteBuffer.put(finishType);
            byteBuffer.putLong(System.currentTimeMillis());
            byteBuffer.put(new byte[2]);

            byteBuffer.flip();


            buffer.position((int) currentOffset);
            buffer.put(byteBuffer);
            buffer.put(data);

            long globalOffset = (currentFileIndex << 32) | currentOffset;
            currentOffset += content_size;

            return globalOffset;


        }


        public WholeMsg readMsg(long globalOffset) throws IOException {
            int globalIndex = (int) (globalOffset >> 32);

            int globalOffsetTrue = (int) globalOffset & 0x0000FFFF;


            try (FileChannel channel = FileChannel.open(
                    pathStore.resolve(String.format("%020d", globalIndex)),
                    StandardOpenOption.READ)) {

                // 读取消息头
                ByteBuffer header = ByteBuffer.allocate(32);
                channel.read(header, globalOffsetTrue);
                header.flip();

                // 读取消息体
                ByteBuffer body = ByteBuffer.allocate(MSG_SIZE);
                channel.read(body, globalOffsetTrue + 32);


                WholeMsg build = WholeMsg.builder()
                        .uuid(new UUID(header.getLong(), header.getLong()))
                        .content_size((long) header.getInt())
                        .msTpe(header.get())
                        .finishType(header.get())
                        .timestamp(header.getLong())
                        .remain(header.getChar())
                        .data(body.array())
                        .build();
                header.clear();
                body.clear();

                return build;
            }
//            ByteBuffer byteBuffer = ByteBuffer.allocate(HEADER_SIZE);
//            byteBuffer.putLong(uuid.getMostSignificantBits());
//            byteBuffer.putLong(uuid.getLeastSignificantBits());
//            byteBuffer.putInt(content_size);   //注意这里是 总长度
//            byteBuffer.put(msgType);
//            byteBuffer.put(finishType);
//            byteBuffer.putLong(System.currentTimeMillis());
//            byteBuffer.put(new byte[2]);

        }

        @Builder
        @Data
        static
        class WholeMsg {
            private UUID uuid;
            private Long content_size;
            private Long timestamp;
            private byte msTpe;
            private byte finishType;
            private char remain;
            private byte[] data;

        }

    }

    static class IndexQueue {
        private static final Path fakePath = null;
        private final static int ENTRY_SIZE = 20;  // 20个字节
        @Getter
        private static IndexQueue INSTANCE;

        static {
            INSTANCE = new IndexQueue(fakePath);

        }

        private final Path pathStore;
        private final Map<UUID, IndexFile> kvQueue = new ConcurrentHashMap<>();
        public IndexQueue(Path pathStore) {
            this.pathStore = pathStore;
        }

        public void addIndex(UUID uuid, long globalOffset) {
//
//            computeIfAbsent()
//            语法：map.computeIfAbsent(key, k -> 生成值的逻辑)
//            行为：如果键不存在，会先执行 lambda 表达式生成值，再将 "键 - 值" 对放入 map；如果键已存在，直接返回现有值。
//            优势：生成值的逻辑（这里是new IndexFile(...)）只会在键不存在时执行，避免了无意义的对象创建。

//            putIfAbsent()
//            语法：map.putIfAbsent(key, 预先创建的值)
//            行为：需要先创建值对象，再放入 map。如果键已存在，会丢弃这个预先创建的值，返回现有值。
//            问题：无论键是否存在，new IndexFile(...)都会执行，可能导致无用的对象创建和资源浪费（比如打开文件句柄后又丢弃）。

            IndexFile indexFile = kvQueue.computeIfAbsent(uuid, x -> new IndexFile(pathStore.resolve(uuid.toString() + ".index")));

            indexFile.appendEntry(globalOffset, System.currentTimeMillis());

        }

        public List<Long> getIndexes(UUID conversationId) throws IOException {
            IndexFile indexFile = kvQueue.get(conversationId);
            if (indexFile == null) return Collections.emptyList();
            return indexFile.readAllOffsets();
        }

        private IndexFile getIndexFile(UUID conversationId) {
            return kvQueue.get(conversationId);
        }


    }

    static class IndexFile {


        private final static int DELETE = 0x1111;
        private final static int SURVIVE = 0x2222;
        private final Path pathStore;
        private FileChannel fileChannel;
        private MappedByteBuffer mmapBuffer;
        private int writePosition = 0;
        @Getter
        private int size = 0;


        // 
        public IndexFile(Path path) {

            this.pathStore = path;
            try {
                init();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }


        private void init() throws IOException {

            if (!Files.exists(pathStore)) {
                Files.createDirectories(pathStore.getParent());
                Files.createFile(pathStore);
            }
            fileChannel = FileChannel.open(pathStore, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ);
            long size = Math.max(1024 * 1024, fileChannel.size()); // 最小1MB
            mmapBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, size);
            while (writePosition + IndexQueue.ENTRY_SIZE < mmapBuffer.capacity()) {
                long aLong = mmapBuffer.getLong(writePosition);
                long aLong2 = mmapBuffer.getLong(writePosition + 8);
                if (aLong == 0 && aLong2 == 0) {
                    break;
                }
                writePosition += IndexQueue.ENTRY_SIZE;
            }
            size = writePosition / IndexQueue.ENTRY_SIZE;

        }

        public synchronized void appendEntry(long globalOffset, long timestamp) {
            //这里按道理来说 是没有 必要的 但是由于jdk8的锁做了升级其实可以加防止 出错
            // 检查空间
            if (writePosition + IndexQueue.ENTRY_SIZE > mmapBuffer.capacity()) {
                expand();
            }

            mmapBuffer.position(writePosition);
            mmapBuffer.putLong(globalOffset); //8
            mmapBuffer.putLong(timestamp);//8
            mmapBuffer.putInt(SURVIVE); //4    状态标志 目前默认状态  0x1111   0x2222
            writePosition += IndexQueue.ENTRY_SIZE;
            size++;

        }

        private void expand() {
            try {

                long newSize = fileChannel.size() * 2;
                fileChannel.truncate(newSize);


                // 重新映射
                mmapBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, newSize);


            } catch (IOException e) {
                throw new RuntimeException("Failed to expand index file", e);
            }
        }

        public List<Long> readAllOffsets() {
            List<Long> offsets = new ArrayList<>();
            int position = 0;

            while (position < writePosition) {
                long offset = mmapBuffer.getLong(position);
                if (offset != 0) {
                    offsets.add(offset);
                }
                position += IndexQueue.ENTRY_SIZE;
            }

            return offsets;
        }

        public void clearIndexFile(boolean isClearAll, Integer num) {
            Integer max = writePosition / IndexQueue.ENTRY_SIZE;

            if (!isClearAll) max = num;
            Integer j = 0;
            for (int i = writePosition - IndexQueue.ENTRY_SIZE; i >= 0 && j <= max; i -= IndexQueue.ENTRY_SIZE) {

                mmapBuffer.position(i + 8 + 8);
                if (mmapBuffer.getInt() == SURVIVE) {
                    j++;
                    mmapBuffer.putInt(DELETE);
                }
            }
        }
    }

}
